package richrail.domain;

import java.util.Collection;
import java.util.HashMap;
import java.util.Observable;

import richrail.tools.Logger;

/**
 * A garage that keeps all trains and wagons
 * 
 * @author Jan Michiel ter Reehorst
 *
 */
public class Garage extends Observable {
	
	HashMap<String, TrainUnit> units;
	private static final String GLUE = ":";
	
	/**
	 * These are strings that format output
	 */
	private static final String
		CREATED 			= "%s %s created",
		CREATEDWITHSEATS 	= "%s %s created with %s seats",
		DELETED				= "%s %s deleted",
		WAGONTOTRAIN		= "wagon %s added to train %s",
		WAGONFROMTRAIN		= "wagon %s removed from train %s",
		DOESNOTEXIST		= "%s %s does not exist",
		NUMSEATS			= "number of seats in %s %s: %s";
	
	/**
	 * Constructor
	 */
	public Garage() {
		this.units = new HashMap<String, TrainUnit>();
	}
	
	/**
	 * Add a new unit with <code>type</code>, <code>id</code> 
	 * and <code>numSeats</code>
	 * 
	 * @param type
	 * @param id
	 * @param numSeats
	 * @return The newly created unit
	 */
	public TrainUnit newUnit(String type, String id, int numSeats) {
		TrainUnit unit = 
			this.units.put(type+GLUE+id, new TrainUnit(type,id,numSeats));
		this.logAndUpdate(CREATEDWITHSEATS, type, id, numSeats);
		return unit;
	}
	
	/**
	 * Add a new unit with <code>type</code> and <code>id</code>	
	 *
	 * @param type
	 * @param id
	 * @return The newly created unit
	 */
	public TrainUnit newUnit(String type, String id) {
		TrainUnit unit = 
			this.units.put(type+GLUE+id, new TrainUnit(type,id));
		this.logAndUpdate(CREATED, type, id);
		return unit;
	}
	
	/**
	 * Completely delete a unit from the <tt>Garage</tt>
	 * 
	 * @param type
	 * @param id
	 * @return The deleted unit 
	 */
	public TrainUnit delete(String type, String id) {
		if(getUnit(type,id) != null) {
			TrainUnit unit = this.units.remove(type+GLUE+id);
			this.logAndUpdate(DELETED, type, id);
			return unit;
		}
		return null;
	}
	
	/**
	 * Add a wagon to a train using their id's
	 * 
	 * @param wagonId
	 * @param trainId
	 * @return Return <code>true</code> if successful, <code>false</code> otherwise 
	 */
	public boolean addWagonToTrain(String wagonId, String trainId) {
		TrainUnit wagon = this.getUnit(TrainUnit.WAGON, wagonId);
		TrainUnit train = this.getUnit(TrainUnit.TRAIN, trainId);
		
		if(train != null && wagon != null && train.addUnit(wagon)) {
			this.logAndUpdate(WAGONTOTRAIN, wagonId, trainId);
			return true;
		}
		return false;
	}
	
	/**
	 * Remove a wagon from a train using their id's
	 * 
	 * @param wagonId
	 * @param trainId
	 * @return Return <code>true</code> if successful, <code>false</code> otherwise 
	 */
	public boolean removeWagonFromTrain(String wagonId, String trainId) {
		TrainUnit wagon = this.getUnit(TrainUnit.WAGON, wagonId);
		TrainUnit train = this.getUnit(TrainUnit.TRAIN, trainId);
		
		if(train != null && wagon != null && train.removeUnit(wagon)) {
			this.logAndUpdate(WAGONFROMTRAIN, wagonId, trainId);
			return true;
		}
		return false;
	}
	
	/**
	 * Get a <tt>TrainUnit</tt> from the garage
	 * 
	 * @param type
	 * @param id
	 * @return The unit with type <code>type</code> and id <code>id</code> 
	 * or <code>null</code> if the unit does not exist
	 */
	private TrainUnit getUnit(String type, String id) {
		TrainUnit unit = this.units.get(type+GLUE+id);
		if(unit != null && unit.isType(type)) {
			return unit;
		} else {
			Logger.getInstance().log(String.format(DOESNOTEXIST, type, id));
			return null;
		}
	}
	
	/**
	 * 
	 * @return All units in the <tt>Garage</tt>
	 */
	public Collection<TrainUnit> getUnits() {
		return this.units.values();
	}
	
	/**
	 * Retrieve the number of seats by type and id
	 * 
	 * @param type
	 * @param id
	 * @return Number of seats
	 */
	public int getNumSeats(String type, String id) {
		TrainUnit unit = this.getUnit(type, id);
		
		if(unit != null) {
			int seats = unit.getNumSeats();
			this.logAndUpdate(NUMSEATS,type,id,seats);
			return seats;
		} else {
			return 0;
		}
	}
	
	/**
	 * Logs a change using String.format(). Then update all observers
	 * 
	 * @param format
	 * @param args
	 */
	private void logAndUpdate(String format, Object ... args) {
		Logger.getInstance().log(String.format(format, args));
		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * String format of the <tt>Garage</tt><br/>
	 * Wagons:<br/>
	 * <tt>wagon1.toString()-wagon2.toString()</tt><br/>
	 * Trains:<br/>
	 * <tt>train1.toString()-wagon.toString()-wagon.toString()</tt><br/>
	 * <tt>train2.toString()-wagon.toString()</tt><br/>
	 * 
	 * @see richrail.domain.TrainUnit#toString()
	 */
	public String toString() {
		String wagonString = "Wagons:\n";
		String trainString = "Trains:\n";
		for(TrainUnit unit : this.getUnits()) {
			if(unit.isType(TrainUnit.TRAIN)) {
				trainString += unit.toString()+"\n";
			} else if(unit.isType(TrainUnit.WAGON)) {
				wagonString += unit.toString();
			}
		}		
		return wagonString+"\n"+trainString;
	}
}
